Искусство схемотехники. Том 3 [Изд.4-е] - Пауль Хоровиц
Шрифт:
Интервал:
Закладка:
Обработчик прерываний. Наконец мы добрались до обработчика прерываний — центральной фигуры всей программы. Перед нами четыре точки входа в обработчик, инициируемый прерываниями от таймера; перед нами также простенький обработчик bad__int ложных прерываний, а также и всех остальных векторизованных ошибок и ловушек (табл. 11.5). Займемся ради разминки программой bad__int, а когда не останется отговорок, примемся за обработчик прерываний от таймера.
МП 68008, как уже описывалось выше, распознает прерывания, а также разнообразные «исключения», перечисленные в таблице, и сохранив в стеке текущие PC и SR, осуществляет переход на команду, адрес которой извлекается из вектора, соответствующего данному исключению. Так, если вы попытаетесь разделить на нуль, ЦП сохранит в стеке содержимое счетчика команд и регистра состояния, а затем перейдет на команду, 32-разрядный адрес которой хранится в байтах памяти с абсолютными адресами $014—$017. Точно так же обслуживаются и прерывания, причем для векторов прерываний с полным подтверждением отведены ячейки с адресами $100-$3FF, а для векторов автовекторизуемых прерываний — ячейки $064-$07F. Вы можете выполнять в обработчике прерываний любые действия; завершить их следует командой RTE (возврат из исключения). Чтобы избежать путаницы, ЦП запрещает прерывания после передачи управления обработчику и разрешает их снова при выполнении команды RTE. Если у вас уж слишком закрученный обработчик, вам может понадобиться разрешить прерывания (только более приоритетных уровней) внутри обработчика, что можно сделать, послав соответствующий байт в регистр состояния.
Программа bad__int. Из рис. 11.20 и текста программы 11.3 легко представить ход выполнения программы bad__int, в задачу которой входит упорядоченный сброс выходных сигналов и вывод на ЭЛД какой-то бросающейся в глаза информации. Стартовый адрес этой программы, определяемый компоновщиком после сборки всех настраиваемых строк, загружается (главной программой в процессе начальной загрузки) во все зарезервированные для векторов ячейки (в начале памяти), перечисленные в таблице. Любое исключение или ложное прерывание (т. е. что угодно, кроме прерывания уровня 5) заставляет ЦП выполнить описанную выше процедуру с передачей управления на программу bad__int. Сначала выключается сигнал Z-оси, чтобы исключение, случайно возникшее в середине программного импульса Z-оси, не оставило луч дисплея включенным на полную яркость (к тому же в одной точке). Далее стоит сбросить сигнал на выходе РАЗВЕРТКА и установить сигнал на выходе КОНЕЦ, поскольку в предшествующих измерениях все равно нет смысла.
Теперь проявим остроумие. Пошлем в порт ЭЛД 01Н и войдем в бесконечный цикл, в котором это число циклически сдвигается влево и после биологически заметной задержки снова посылается на ЭЛД. Результатом такой операции будет «шагающий бит» на ЭЛД-индикаторе, картина, которая заставит встрепенуться самого измученного оператора. Поскольку в цикле нет команды RTE, процесс этот будет идти бесконечно. Чтобы снова начать измерения, оператор должен нажать кнопку СБРОС.
Упражнение 11.15. Придумайте более совершенный алгоритм, позволяющий оператору определить, какое исключение привело к сбою. Подсказка: всего имеется немного менее 256 исключений; ЭЛД-индикатор содержит 8 бит. Можете ли вы написать программу, реализующую ваше решение?
Прерывания от таймера: четыре точки входа. Теперь у нас не осталось никаких отговорок. Нырнем. Текст обработчика прерываний входит в программу 11.3; его структурная схема изображена на рис. 11.21.
Рис. 11.21. Структурная схема обработчика прерываний.
Обработчик имеет четыре точки входа, соответствующие различным состояниям прибора. Они обозначены idle, wait__trig, sweep__start и get__data. Программа, в зависимости от общего состояния прибора, автоматически изменяет содержимое вектора прерываний (ячейка $074), связывая прерывание с той или иной точкой входа. Если вы не желаете накапливать данные, вы входите в обработчик в точке idle; на экран выводится одна точка и осуществляется возврат. Если войти в обработчик в точке get__data, программа считывает АЦП, проверяет, не возникли ли состояния «конец ячейки» или «конец развертки» (обрабатывая их соответствующим образом) и обновляет дисплей. При входе в точке sweep__start устанавливается требуемое состояние ЭЛД и выходных сигналов и осуществляется переход в точку get__data.
Наконец, вход wait__trig служит для проверки наличия сигнала внешнего запуска и перехода либо на sweep__strat, либо на idle. В обработчике прерываний имеются и другие метки (например z__pulse), но они не являются входными точками, а служат для переходов внутри программы.
Прерывания от таймера: idle. Учитывая важность обработчика, рассмотрим его во всех деталях. Ранее в главной программе вектор прерываний был настроен на вход idle, чтобы в ожидании запуска образовать изображение на экране. Таким образом, выполнение начинается с метки idle__int. Если вспомнить назначение зарезервированных регистров, понять ход программы не сложно. В D4 хранится индекс очередной точки экрана, требующей регенерации, который мы посылаем в преобразователь Х-координаты ЦАПО (используя косвенную адресацию со смещением, которая быстрее абсолютной). В преобразователь Y-координаты ЦАП1 мы посылаем данное (используя D4 в качестве индекса массива DISPLAY, указатель базы которого находится в А4). D4 инкрементируется (но не проверяется на конец массива) и управление передается генератору импульса Z-оси.
Упражнение 11.16. Объясните, почему можно обойтись без проверки индексного регистра D4 массива DISPLAY после его инкрементирования?
К этому времени Х- и Y-ЦАП уже установились (время установки 1 мкс), поэтому генератор Z-импульса с помощью команды BSET устанавливает бит Z__BLANK (бит 4, см. определения) параллельного порта В, адрес которого, ввиду его частого использования, мы храним в регистре А2. Сбросить бит можно следующей командой, но в этом случае образовался бы слишком короткий (3 мкс) импульс, и изображение было бы бледным (подсветка на 3 мкс каждые 100 мкс). Поскольку, однако, все прерывания завершаются через этот программный блок, мы можем воспользоваться возможностью и сделать полезное дело, одновременно убив время, именно, сообщить таймеру, что он может снять свой запрос на прерывание. Запись в регистр команд и состояния таймера-1 осуществляется с помощью двухэтапного процесса (как это было и в блоке инициализации главной программы): сначала мы посылаем в управляющий регистр микросхемы (адрес $84003) внутренний адрес регистра ($0А), а затем посылаем сам управляющий байт ($20), который интерпретируется микросхемой 8536, как команда на снятие запроса прерывания от таймера-1. Больше до выхода из прерывания ничего делать не нужно, поэтому мы завершаем импульс Z-оси (командой BCLR) и выполняем команду RTE (возврат из исключения). Поместив строки подтверждения прерывания в генератор Z-импульса, мы удлинили импульс подсветки до 10 мкс, с повторением его каждые 100 мкс. Прерывание все равно надо было подтвердить, и мы нашли для этого самое подходящее место. Такая же глюковина использована нами и в другом месте, когда мы в течение аналого-цифрового преобразования посылаем в ЦАП X и Y-координаты точки. Об этом ниже.
Прерывания от таймера: get_data. Эта точка входа используется чаще других, именно, когда усреднитель сигнала выполняет развертку. Мы запускаем АЦП, посылая в его порт байт режима ($03); это число определяет биполярное преобразование в дополнительном коде. Как и раньше, для повышения скорости мы используем косвенную адресацию через регистр A3 (в котором хранится адрес АЦП).
Теперь надо подождать 10 мкс окончания преобразования — прекрасная возможность послать на ЦАП дисплея новую пару X и Y-координат точно так же, как это делается в блоке idle. Эти программные действия заканчиваются на 1 мкс раньше, чем нужно, поэтому мы тянем время с помощью команды NOP (холостая команда), а затем считываем АЦП. Заметьте, насколько это удобнее, чем вводить бит состояния, сигнализирующий о завершении преобразования в АЦП (эта возможность обсуждалась в разд. 11.05); не забудьте, однако, добавить еще несколько команд NOP, если вам захочется увеличить тактовую частоту ЦП.
Мы прочитали из АЦП байт в дополнительном (до 2) коде, но наш массив DATA и накопитель ячейки (D7) используют длинные дополнительные числа. Для получения длинного целого числа дважды выполняется команда ЕХТ (расширение знака). Расширение знака представляет собой просто копирование самого старшего бита числа влево, пока не заполнится большее по длине целое слово; эта операция сохраняет значение целого со знаком (простое заполнение нулями не сохраняет значения числа). Расширенное целое добавляется к накапливаемому содержимому ячейки в D7, а счетчик ширины канала dwell__per__bin (D6) декрементируется. Если в нем еще не нуль, возврат осуществляется через z__pulse, как описано выше. Полное время выполнения программы обработчика в этом случае составляет 32,3 мкс плюс 9 мкс на процедуру прерывания ЦП и еще 5 мкс на команду RTE, всего 46,3 мкс. Таким образом, главная программа имеет более половины процессорного времени на выполнение простой задачи обновления массива DISPLAY.